#include "vtsreadfilter.hpp"
#include "cmemallocator.hpp"
#include <vfwmsgs.h>
#include <uuids.h>
#include <cassert>
//#include <amvideo.h>
//#include <dvdmedia.h>
//#include <process.h>
//#include <comdef.h>
//#ifdef _DEBUG
//#include "odbgstream.hpp"
//using std::endl;
//using std::dec;
//using std::hex;
//#endif


namespace VTSRead
{


Outpin::Outpin(Filter* p)
    : Pin(p, L"outpin")
{
    AM_MEDIA_TYPE mt;
    
    //http://msdn.microsoft.com/en-us/library/dd390716(VS.85).aspx
    
    //TODO: it's not clear whether the preferred media type
    //is determined apriori, or from the mediatype arg
    //passed to Filter::Load.
    
    //This works with the MMSPlitter, but does NOT work with MPEG2Demultiplexer.
    mt.majortype = MEDIATYPE_Stream;
    mt.subtype = MEDIASUBTYPE_MPEG2_PROGRAM;
    mt.bFixedSizeSamples = FALSE;
    mt.bTemporalCompression = FALSE;
    mt.lSampleSize = 0;
    mt.formattype = FORMAT_None;
    mt.pUnk = 0;
    mt.cbFormat = 0;
    mt.pbFormat = 0;
    
    m_preferred.Add(mt);
}


Outpin::~Outpin()
{
}   


HRESULT Outpin::QueryInterface(const IID& iid, void** ppv)
{
    if (ppv == 0)
        return E_POINTER;
        
    IUnknown*& pUnk = reinterpret_cast<IUnknown*&>(*ppv);
    
    if (iid == __uuidof(IUnknown))
        pUnk = static_cast<IPin*>(this);
        
    else if (iid == __uuidof(IPin))
        pUnk = static_cast<IPin*>(this);
        
    else if (iid == __uuidof(IAsyncReader))
        pUnk = static_cast<IAsyncReader*>(this);

    else
    {
        pUnk = 0;
        return E_NOINTERFACE;
    }
    
    pUnk->AddRef();
    return S_OK;
}


ULONG Outpin::AddRef()
{
    return m_pFilter->AddRef();
}


ULONG Outpin::Release()
{
    return m_pFilter->Release();
}
    

HRESULT Outpin::Connect(
    IPin* pin,
    const AM_MEDIA_TYPE* pmt)
{
    if (pin == 0)
        return E_POINTER;
        
    Filter::Lock lock;
    
    HRESULT hr = lock.Seize(m_pFilter);
    
    if (FAILED(hr))
        return hr;
    
    if (m_pFilter->m_files.empty())
        return E_UNEXPECTED;

    if (m_connection)
        return VFW_E_ALREADY_CONNECTED;
        
    //if (m_pFilter->m_state != State_Stopped)
    //    return VFW_E_NOT_STOPPED;
        
    assert(m_connection_mt.Empty());
    
    if (pmt)
    {
        hr = QueryAccept(pmt);
         
        if (hr != S_OK)
            return VFW_E_TYPE_NOT_ACCEPTED;
            
        hr = pin->ReceiveConnection(this, pmt);
        
        if (FAILED(hr))
            return hr;
            
        m_connection_mt.Add(*pmt);        
    }
    else
    {
        assert(m_preferred.Size() == 1);
        const AM_MEDIA_TYPE& mt = m_preferred[0];

        hr = pin->ReceiveConnection(this, &mt);
        
        if (FAILED(hr))
            return hr;
            
        m_connection_mt.Add(mt);        
    }

    m_connection = pin;
    m_connection->AddRef();
    
    return S_OK;
}


#if 0
HRESULT Outpin::Disconnect()
{
    //TODO: what to do about pending reads?
    
    Filter::Lock lock(m_pFilter);

    if (m_pFilter->m_state != State_Stopped)
        return VFW_E_NOT_STOPPED;

    if (m_meminput)
    {
        m_meminput->Release();
        m_meminput = 0;
    }
    
    if (m_memalloc)
    {
        m_memalloc->Release();
        m_memalloc = 0;
    }
    
    return Pin::Disconnect();
}
#endif


HRESULT Outpin::ReceiveConnection( 
    IPin*,
    const AM_MEDIA_TYPE*)
{
    return E_UNEXPECTED;  //for input pins only
}


HRESULT Outpin::QueryAccept(const AM_MEDIA_TYPE* pmt)
{
    if (pmt == 0)
        return E_POINTER;
        
    const AM_MEDIA_TYPE& mt = *pmt;
    
    if (mt.majortype == MEDIATYPE_Stream)
        return S_OK;
        
    return S_FALSE;
}


HRESULT Outpin::QueryInternalConnections(IPin**, ULONG* pn)
{
    if (pn == 0)
        return E_POINTER;
        
    *pn = 0;
    return S_OK;        
}

        
HRESULT Outpin::EndOfStream()
{
    return E_UNEXPECTED;  //for inpins only
}


HRESULT Outpin::NewSegment(
    REFERENCE_TIME,
    REFERENCE_TIME,
    double)
{
    return E_UNEXPECTED;
}


HRESULT Outpin::RequestAllocator( 
    IMemAllocator* pPreferred,
    ALLOCATOR_PROPERTIES* pProps,
    IMemAllocator** ppActual)
{
    Filter::Lock lock;
    
    HRESULT hr = lock.Seize(m_pFilter);
    
    if (FAILED(hr))
        return hr;
    
    if (m_pFilter->m_files.empty())
        return E_UNEXPECTED;

    if (pPreferred == 0)
    {
        const HRESULT hr = CMemAllocator::CreateInstance(ppActual);
        
        if (FAILED(hr))
            return hr;
    }
    else
    {
        if (ppActual == 0)
            return E_POINTER;

        IMemAllocator*& pActual = *ppActual;
        
        pActual = pPreferred;
        pActual->AddRef();
    }
    
    ALLOCATOR_PROPERTIES props;
    
    if (pProps == 0)  //weird
    {
        props.cBuffers = 1;
        props.cbBuffer = 64 * 1024;  //?
        props.cbAlign = 1;
        props.cbPrefix = 0;
    }
    else
    {
        props = *pProps;
        
        if (props.cBuffers <= 0)
            props.cBuffers = 1;
            
        if (props.cbBuffer <= 0)
            props.cbBuffer = 64 * 1024;  //?
            
        if (props.cbAlign <= 0)
            props.cbAlign = 1;
            
        if (props.cbPrefix < 0)
            props.cbPrefix = 0;
    }
        
    IMemAllocator*& pActual = *ppActual;

    hr = pActual->SetProperties(&props, &m_props);
    
    if (SUCCEEDED(hr))
        return hr;
        
    pActual->Release();
    pActual = 0;
    
    return hr;
}

    
HRESULT Outpin::Request( 
    IMediaSample* pSample,
    DWORD_PTR user)
{
    if (pSample == 0)
        return E_INVALIDARG;
        
    __int64 st, sp;
    
    HRESULT hr = pSample->GetTime(&st, &sp);
    
    if (hr != S_OK)  //need both start and stop
        return VFW_E_SAMPLE_TIME_NOT_SET;
        
    if (st % 10000000)
        return E_INVALIDARG;  //?
        
    if (sp % 10000000)
        return E_INVALIDARG;  //?
        
    const __int64 start = st / 10000000;
    
    const long size = pSample->GetSize();
    
    if (size <= 0)
        return E_INVALIDARG;

    Filter::Lock lock;
    
    hr = lock.Seize(m_pFilter);
    
    if (FAILED(hr))
        return hr;
    
    if (m_pFilter->m_files.empty())
        return E_UNEXPECTED;
    
    if (m_bFlush)
        return VFW_E_WRONG_STATE;
        
    //TODO: check alignment
    
    if (start >= m_pFilter->m_length)
        return HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
        
    //TODO: check for duplicate key
        
    m_read_queue.push_back(ReadInfo(pSample, user));
    
    return S_OK;
}
    
    
HRESULT Outpin::WaitForNext( 
    DWORD /* timeout */ ,
    IMediaSample** ppSample,
    DWORD_PTR* pUser)
{
    if (ppSample == 0)
        return E_POINTER;
        
    IMediaSample*& pSample = *ppSample;
    pSample = 0;
        
    Filter::Lock lock;
    
    HRESULT hr = lock.Seize(m_pFilter);
    
    if (FAILED(hr))
        return hr;
    
    if (m_pFilter->m_files.empty())
        return E_UNEXPECTED;

    if (m_bFlush)
    {
        if (!m_read_queue.empty())
        {
            ReadInfo& r = m_read_queue.front();
            
            pSample = r.m_pSample;
            assert(pSample);
            
            hr = pSample->SetActualDataLength(0);
            
            if (pUser)
                *pUser = r.m_user;
                
            m_read_queue.pop_front();
        }
                
        return VFW_E_WRONG_STATE;
    }
    
    if (m_read_queue.empty())
    {
        if (pUser)
            *pUser = 0;
            
        return S_OK;  //?
    }
    
    {
        ReadInfo& r = m_read_queue.front();
        
        pSample = r.m_pSample;
        assert(pSample);
        
        if (pUser)
            *pUser = r.m_user;

        m_read_queue.pop_front();
    }        
    
    __int64 st, sp;
    
    hr = pSample->GetTime(&st, &sp);
    assert(hr == S_OK);
    assert(st % 10000000 == 0);
    assert(sp % 10000000 == 0);

    const __int64 start = st / 10000000;
    assert(start < m_pFilter->m_length);  //TODO: allow this?
    
    const __int64 stop = sp / 10000000;
    
    const __int64 requested_len = stop - start + 1;
    
    if (requested_len <= 0)
    {
        pSample->SetActualDataLength(0);
        return S_OK;
    }
    
    const long size = pSample->GetSize();
    assert(size > 0);
    
    BYTE* ptr;
    
    hr = pSample->GetPointer(&ptr);
    assert(SUCCEEDED(hr));
    assert(ptr);
    
    long n;
    
    if (requested_len <= size)
        n = static_cast<long>(requested_len);
    else
        n = size;
    
    const long m = m_pFilter->Read(start, n, ptr);    
    
    if (m < 0)
        return E_FAIL;
        
    pSample->SetActualDataLength(m);
        
    if (m < n)
        return S_FALSE;
        
    if (requested_len <= size)
        return S_OK;
        
    //TODO: decide whether this case should be allowed
    //I don't quite understand the aligned I/O yet.
        
    return S_FALSE;
}
    

HRESULT Outpin::SyncReadAligned(IMediaSample* pSample)
{
    if (pSample == 0)
        return E_INVALIDARG;
        
    __int64 st, sp;
    
    HRESULT hr = pSample->GetTime(&st, &sp);
    
    if (hr != S_OK)  //need both start and stop
        return VFW_E_SAMPLE_TIME_NOT_SET;
        
    if (st % 10000000)
        return E_INVALIDARG;  //?
        
    if (sp % 10000000)
        return E_INVALIDARG;  //?
        
    const __int64 start = st / 10000000;
    
    const long size = pSample->GetSize();
    
    if (size <= 0)
        return E_INVALIDARG;

    Filter::Lock lock;
    
    hr = lock.Seize(m_pFilter);
    
    if (FAILED(hr))
        return hr;
    
    if (m_pFilter->m_files.empty())
        return E_UNEXPECTED;
    
    //TODO: need to check this here?
    //if (m_bFlush)
    //    return VFW_E_WRONG_STATE;
        
    //TODO: check alignment
    
    if (start >= m_pFilter->m_length)
        return HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
        
    const __int64 stop = sp / 10000000;
    
    const __int64 requested_len = stop - start + 1;
    
    if (requested_len <= 0)
    {
        pSample->SetActualDataLength(0);
        return S_OK;
    }
    
    BYTE* ptr;
    
    hr = pSample->GetPointer(&ptr);
    assert(SUCCEEDED(hr));
    assert(ptr);
    
    long n;
    
    if (requested_len <= size)
        n = static_cast<long>(requested_len);
    else
        n = size;
    
    const long m = m_pFilter->Read(start, n, ptr);
    
    if (m < 0)
        return E_FAIL;

    pSample->SetActualDataLength(m);
            
    if (m < n)
        return S_FALSE;
        
    if (requested_len <= size)
        return S_OK;

    //TODO: decide whether this case should be allowed
    //I don't quite understand the aligned I/O yet.

    return S_FALSE;
}

    
HRESULT Outpin::SyncRead( 
    LONGLONG start,
    LONG len,
    BYTE* ptr)
{
    if (start < 0)
        return E_INVALIDARG;
        
    if (len <= 0)
        return S_OK;        
    
    Filter::Lock lock;
    
    HRESULT hr = lock.Seize(m_pFilter);
    
    if (FAILED(hr))
        return hr;
    
    if (m_pFilter->m_files.empty())
        return E_UNEXPECTED;
    
    //TODO: need to check this here?
    //if (m_bFlush)
    //    return VFW_E_WRONG_STATE;
    
    if (start >= m_pFilter->m_length)
        return HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);

    const long m = m_pFilter->Read(start, len, ptr);
    
    if (m < 0)
        return E_FAIL;
        
    return (m >= len) ? S_OK : S_FALSE;
}

    
HRESULT Outpin::Length( 
    LONGLONG* pTotal,
    LONGLONG* pAvailable)
{
    Filter::Lock lock;
    
    HRESULT hr = lock.Seize(m_pFilter);
    
    if (FAILED(hr))
        return hr;
        
    if (m_pFilter->m_files.empty())
        return E_UNEXPECTED;
        
    if (pTotal)
        *pTotal = m_pFilter->m_length;
        
    if (pAvailable)
        *pAvailable = m_pFilter->m_length;
                
    return S_OK;
}


HRESULT Outpin::BeginFlush()
{
    Filter::Lock lock;
    
    HRESULT hr = lock.Seize(m_pFilter);
    
    if (FAILED(hr))
        return hr;
    
    m_bFlush = true;
    
    return S_OK;
}


HRESULT Outpin::EndFlush()
{
    Filter::Lock lock;
    
    HRESULT hr = lock.Seize(m_pFilter);
    
    if (FAILED(hr))
        return hr;
    
    m_bFlush = false;
    
    return S_OK;
}
    

} //end namespace VTSRead
